package world.snowcrystal.openapigateway;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import world.snowcrystal.commons.exception.BusinessException;
import world.snowcrystal.commons.model.dto.rpc.*;
import world.snowcrystal.commons.model.entity.InterfaceInfo;
import world.snowcrystal.commons.model.entity.User;
import world.snowcrystal.commons.model.enums.ApplicationResponseStatusCode;
import world.snowcrystal.commons.model.enums.SnowcrystalHeaders;
import world.snowcrystal.commons.service.rpc.RpcInterfaceService;
import world.snowcrystal.commons.service.rpc.RpcUserService;

import java.net.InetSocketAddress;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    private static final List<String> IP_WHITE_LIST = List.of("127.0.0.1");
    private static final String SALT = "wqeiprqweioprhwsdjiofhndofhn";
    private static final long FIVE_MINUTES = 5 * 60 * 1000;


    @Lazy
    private RpcInterfaceService rpcInterfaceService;

    @Lazy
    private RpcUserService rpcUserService;

    @Autowired
    @Lazy
    public void setRpcInterfaceService(RpcInterfaceService rpcInterfaceService) {
        this.rpcInterfaceService = rpcInterfaceService;
    }

    @Autowired
    @Lazy
    public void setRpcUserService(RpcUserService rpcUserService) {
        this.rpcUserService = rpcUserService;
    }

    private ApplicationContext applicationContext;

    public CustomGlobalFilter() {
        log.info("CustomGlobalFilter init");
    }

    @Override
    public int getOrder() {
        return -1;
    }

    private Mono<Void> handleFailure(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.FORBIDDEN);
        return response.setComplete();
    }

    private Mono<Void> handleFailure(ServerHttpResponse response, String message) {
        response.setStatusCode(HttpStatus.FORBIDDEN);
        DataBufferFactory dataBufferFactory = response.bufferFactory();
        DataBuffer buffer = dataBufferFactory.wrap(message.getBytes());
        return response.writeWith(Flux.just(buffer));
    }

    private String getHeader(ServerHttpRequest request, SnowcrystalHeaders header) {
        final List<String> headers = request.getHeaders().get(header.value());
        if (headers == null || headers.isEmpty()) {
            throw new RuntimeException("Some Information are not found");
        }
        return headers.get(0);
    }


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        final Map<String, String> pathVariables = (Map<String, String>) exchange.getAttributes()
                .get(ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        final ServerHttpRequest request = exchange.getRequest();
        final String requestId = request.getId();
        final ServerHttpResponse response = exchange.getResponse();
        final InetSocketAddress remoteAddress = request.getRemoteAddress();
        String requestMethod = request.getMethod().name();
        URI uri = request.getURI();
        assert remoteAddress != null;
        final String ip = remoteAddress.getAddress().getHostAddress();

        // region 请求日志
        log.info("Request [{}] :Receive request {} {} from {}",
                requestId,
                request.getMethod().name(),
                pathVariables.get("spring"),
                remoteAddress);
        // endregion 请求日志

        // region IP 黑白名单
        if (!IP_WHITE_LIST.contains(ip)) {
            log.info("Request [{}] : IP {} is not allowed", requestId, ip);
            return handleFailure(response);
        }
        // endregion IP 黑白名单

        // region retrieve variables;
        String signature;
        String accessKey;
        String nonce;
        Long timestamp;
        String desiredHost;
        try {
            // NPE  | CastException | RuntimeException
            signature = getHeader(request, SnowcrystalHeaders.SIGNATURE_HEADER);
            accessKey = getHeader(request, SnowcrystalHeaders.ACCESS_KEY_HEADER);
            nonce = getHeader(request, SnowcrystalHeaders.NONCE_HEADER);
            timestamp = Long.valueOf(getHeader(request, SnowcrystalHeaders.TIMESTAMP_HEADER));
            // host:port
            desiredHost = getHeader(request, SnowcrystalHeaders.REQUEST_HOST_HEADER);
        } catch (Exception e) {
            return handleFailure(response, e.getMessage());
        }
        if (System.currentTimeMillis() - timestamp > FIVE_MINUTES) {
            log.info("Request {} :Timestamp {} is too old", requestId, timestamp);
            return handleFailure(response);
        }
        // endregion retrieve variables;

        // region nonce 检查
        // TODO nonce 检查
        // endregion nonce 检查

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(uri.getScheme())
                .append("://")
                .append(desiredHost)
                .append(uri.getPath());
        String desiredUrl = stringBuilder.toString();
        // region reactive style
        AtomicReference<User> user = new AtomicReference<>(null);
        AtomicReference<InterfaceInfo> interfaceInfo = new AtomicReference<>(null);
        return fromCompletable(() -> rpcUserService.getInvoker(RetrieveInvokerRequest.builder().accessKey(accessKey).build()))
                // 检查用户是否存在
                .flatMap(invoker -> {
                    if (invoker == null) {
                        return Mono.error(new BusinessException(ApplicationResponseStatusCode.NOT_FOUND_ERROR, "User not found"));
                    }
                    user.set(invoker);
                    return fromCompletable(() -> rpcUserService.checkAccessSignature(CheckAccessSignatureRequest.builder()
                            .userId(user.get().getId())
                            .accessKey(accessKey)
                            .sign(signature)
                            .build()));
                })

                // 检查用户 ak 与签名是否正确
                .flatMap(validAccess -> {
                    if (Optional.ofNullable(validAccess.getData()).orElse(false)) {
                        // 重新拼接真实地址

                        return fromCompletable(() -> rpcInterfaceService.getInterfaceInfo(RetrieveInterfaceInformationRequest.builder()
                                .url(stringBuilder.toString())
                                .method(requestMethod)
                                .build()));
                    } else {
                        return Mono.error(new BusinessException(ApplicationResponseStatusCode.NO_AUTH_ERROR, "AccessKey not authorized"));
                    }
                })

                // 检查接口是否存在
                .flatMap(inf -> {
                    if (inf == null) {
                        return Mono.error(new BusinessException(ApplicationResponseStatusCode.NOT_FOUND_ERROR, "Interface not found"));
                    } else {
                        interfaceInfo.set(inf);
                        return fromCompletable(() -> rpcInterfaceService.retrieveRemainingInvokeCount(InterfaceIdAndUserIdRequest.builder()
                                .userId(user.get().getId())
                                .interfaceId(inf.getId())
                                .build()));
                    }
                })

                //  该用户对于该接口的剩余调用次数大于 0
                .flatMap(integerSimpleRpcResponse -> {
                    Integer remainInvokeTimes = Optional.ofNullable(integerSimpleRpcResponse.getData()).orElse(-999999);
                    if (remainInvokeTimes.equals(-999999)) {
                        return Mono.error(new BusinessException(ApplicationResponseStatusCode.SYSTEM_ERROR, "System error"));
                    } else if (remainInvokeTimes.equals(-1)) {
                        return Mono.error(new BusinessException(ApplicationResponseStatusCode.NO_AUTH_ERROR, "你还没有开通调用这个接口的权限"));
                    } else if (remainInvokeTimes == 0) {
                        return Mono.error(new BusinessException(ApplicationResponseStatusCode.NO_AUTH_ERROR, "你没有这个接口的调用次数了"));
                    }
                    return chain.filter(exchange);
                })

                // 接口存在，则放行后，在网关响应给客户端时执行以下 then 操作
                .then(Mono.fromSupplier(() -> {
                    // post filter
                    HttpStatusCode statusCode = Optional.ofNullable(response.getStatusCode()).orElse(HttpStatus.NOT_MODIFIED);
                    if (statusCode.is2xxSuccessful()) {
                        log.info("Request [{}] : interface -{}-{}-- invoke success ", requestId, requestMethod, desiredUrl);
                        return fromCompletable(() -> rpcInterfaceService.increaseInterfaceInvokeCount(InterfaceIdAndUserIdRequest.builder()
                                .interfaceId(interfaceInfo.get().getId())
                                .userId(user.get().getId())
                                .build()));
                    } else {
                        return Mono.error(new BusinessException(ApplicationResponseStatusCode.OPERATION_ERROR,
                                "Request [" + requestId + "received a bad response(" + statusCode.value() + ") from the interface."));
                    }
                }))
                .flatMap(countResult -> countResult)
                .flatMap(countResult -> {
                    try {
                        SimpleRpcResponse<Boolean> result = (SimpleRpcResponse<Boolean>) countResult;
                        if (Optional.ofNullable(result.getData()).orElse(false)) {
                            log.info("Request [{}] : interface -{}--{}-- invoke is successfully counted.", requestId, requestMethod, desiredUrl);
                        } else {
                            return Mono.error(new BusinessException(ApplicationResponseStatusCode.OPERATION_ERROR,
                                    "Request [" + requestId + " fail to take invoke count."));
                        }
                        return Mono.empty();
                    } catch (ClassCastException e) {
                        assert countResult instanceof Throwable;
                        return Mono.error(new BusinessException(ApplicationResponseStatusCode.OPERATION_ERROR,
                                "Request [" + requestId + " fail to take invoke count."));
                    }
                })
                .onErrorResume((e) -> handleError(e, requestId, desiredUrl))
                .then(Mono.empty());
        // endregion reactive style

        // region blocking style
        //        // 如果操作较为复杂，则可以由 backend 增删改查项目提供接口
        // 由于 ak/sk 穿过来的是前面的形式，无法直接进行查询，所以，可以同时传一个 ak 和 ak/sk 的前面
        // 通过 ak 查询判断用户是否存在，然后得出用户的 sk 再进行校验
        // RPC

//        User invokeUser = rpcUserService.getInvoker(accessKey);
//        if (invokeUser == null) {
//            log.info("Request {} :AccessKey {} is not exist", requestId, accessKey);
//            // 鉴权失败，返回错误码
//            return handleFailure(response);
//        }
//        boolean isValidAccess = rpcUserService.checkAccessSignature(accessKey,signature);
//        if (!isValidAccess) {
//            log.info("Request {} :AccessKey {} is not valid", requestId, accessKey);
//            // 鉴权失败，返回错误码
//            return handleFailure(response);
//        }
//        // 接口是否存在
//        InterfaceInfo interfaceInformation = rpcInterfaceService.getInterfaceInfo(desiredUrl, requestMethod);
//        if (interfaceInformation == null) {
//            log.info("Request {} :Interface {} is not exist", requestId, desiredUrl);
//            // 接口不存在，返回错误码
//            return handleFailure(response);
//        }
//        // 请求转发
//        Mono<Void> mono = chain.filter(exchange);
//        // 响应日志
//        // 调用成功，接口调用次数 ++；需要确保目标接口已经调用完成
//        // RPC
//        // 调用失败，返回错误码
//        mono.then(Mono.fromRunnable(()->{
//            HttpStatusCode statusCode = response.getStatusCode();
//            log.info("Request [{}] : interface {} responses with code {}", requestId, pathVariables.get("spring"), statusCode);
//            if(statusCode.is2xxSuccessful()){
//                boolean updateSuccess = rpcInterfaceService.increaseInterfaceInvokeCount(interfaceInformation.getId(), invokeUser.getId());
//                if(updateSuccess){
//                    log.info("Request [{}] : interface {} responses with code {} and is successfully counted.", requestId, desiredUrl, statusCode);
//                }else{
//                    log.info("Request [{}] : interface {} responses with code {} but failed to count.", requestId, desiredUrl, statusCode);
//                }
//            }
//        }));
//        return mono;
        // endregion blocking style
    }

    private <T> Mono<T> fromCompletable(Supplier<T> supplier) {
        return Mono.fromFuture(CompletableFuture.supplyAsync(supplier));
    }

    private Mono<Void> handleError(Throwable throwable, String requestId, String desiredUrl) {
        log.info("Request [{}] : fail to invoke interface {} : {} ", requestId, desiredUrl, throwable.getMessage());
        throwable.printStackTrace();
        return Mono.empty();
    }
}
